package com.ifonly.security;

import com.google.common.base.Strings;
import com.ifonly.cache.CacheManagerFactory;
import com.ifonly.config.Configuration;
import com.ifonly.core.ClassContainer;
import com.ifonly.logger.Logger;
import com.ifonly.security.cache.ShiroCacheManager;
import com.ifonly.utils.ReflectUtils;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.config.Ini;
import org.apache.shiro.util.CollectionUtils;
import org.apache.shiro.util.StringUtils;
import org.apache.shiro.web.config.IniFilterChainResolverFactory;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.filter.authc.AuthenticationFilter;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;
import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager;
import org.apache.shiro.web.filter.mgt.FilterChainManager;
import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.mgt.WebSecurityManager;
import org.apache.shiro.web.servlet.AbstractShiroFilter;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;

import javax.servlet.*;
import java.io.IOException;
import java.util.List;
import java.util.Map;

/**
 * shiro 拦截,避免 shiro 配置
 *
 * @author <a href="mailto:ifonlymaster@163.com">ifonly</a>
 * @version 1.0 2015-12-31 13:30
 * @since JDK 1.6
 */
public class IfOnlyShiroFilter extends AbstractShiroFilter {
    private Configuration cfg = Configuration.getInstance();
    private String encoding;

    private Logger logger;

    @Override
    public void init() throws Exception {
        super.init();

        this.encoding = cfg.getEncoding();
        this.logger = Logger.getLogger(IfOnlyShiroFilter.class);

        WebSecurityManager webSecurityManager = createSecurityManager();
        FilterChainManager manager = createFilterChainManager();

        //Expose the constructed FilterChainManager by first wrapping it in a
        // FilterChainResolver implementation. The AbstractShiroFilter implementations
        // do not know about FilterChainManagers - only resolvers:
        PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
        chainResolver.setFilterChainManager(manager);

        setSecurityManager(webSecurityManager);
        setFilterChainResolver(chainResolver);
    }

    @Override
    protected void doFilterInternal(ServletRequest req,
                                    ServletResponse resp,
                                    FilterChain chain) throws ServletException, IOException {
        req.setCharacterEncoding(encoding);
        resp.setCharacterEncoding(encoding);

        super.doFilterInternal(req, resp, chain);
    }

    protected FilterChainManager createFilterChainManager() {
        logger.debug("Create FilterChainManager...");

        final DefaultFilterChainManager manager = new DefaultFilterChainManager();
        Map<String, Filter> defaultFilters = manager.getFilters();
        //apply global settings if necessary:
        for (Filter filter : defaultFilters.values()) {
            applyGlobalPropertiesIfNecessary(filter);
        }

        Map<String, String> chains = getFilterChainDefinitionMap();
        if (!CollectionUtils.isEmpty(chains)) {
            for (Map.Entry<String, String> entry : chains.entrySet()) {
                String url = entry.getKey();
                String chainDefinition = entry.getValue();
                manager.createChain(url, chainDefinition);
            }
        }

        return manager;
    }

    public Map<String, String> getFilterChainDefinitionMap() {
        String definitions = cfg.getFilterChainDefinitions();
        definitions = definitions.replaceAll(";", "\r\n");

        Ini ini = new Ini();
        ini.load(definitions);
        //did they explicitly state a 'urls' section?  Not necessary, but just in case:
        Ini.Section section = ini.getSection(IniFilterChainResolverFactory.URLS);
        if (CollectionUtils.isEmpty(section)) {
            //no urls section.  Since this _is_ a urls chain definition property, just assume the
            //default section contains only the definitions:
            section = ini.getSection(Ini.DEFAULT_SECTION_NAME);
        }
        return section;
    }


    private void applyLoginUrlIfNecessary(Filter filter) {
        String loginUrl = cfg.getLoginUrl();
        if (StringUtils.hasText(loginUrl) && (filter instanceof AccessControlFilter)) {
            /*if(filter instanceof FormAuthenticationFilter) {
                ((FormAuthenticationFilter)filter).setRememberMeParam("rememberMe");
            }*/
            AccessControlFilter acFilter = (AccessControlFilter) filter;
            //only apply the login url if they haven't explicitly configured one already:
            String existingLoginUrl = acFilter.getLoginUrl();
            if (AccessControlFilter.DEFAULT_LOGIN_URL.equals(existingLoginUrl)) {
                acFilter.setLoginUrl(loginUrl);
            }
        }
    }

    private void applySuccessUrlIfNecessary(Filter filter) {
        String successUrl = cfg.getSuccessUrl();
        if (StringUtils.hasText(successUrl) && (filter instanceof AuthenticationFilter)) {
            AuthenticationFilter authcFilter = (AuthenticationFilter) filter;
            //only apply the successUrl if they haven't explicitly configured one already:
            String existingSuccessUrl = authcFilter.getSuccessUrl();
            if (AuthenticationFilter.DEFAULT_SUCCESS_URL.equals(existingSuccessUrl)) {
                authcFilter.setSuccessUrl(successUrl);
            }
        }
    }

    private void applyUnauthorizedUrlIfNecessary(Filter filter) {
        String unauthorizedUrl = cfg.getUnauthorizedUrl();
        if (StringUtils.hasText(unauthorizedUrl) && (filter instanceof AuthorizationFilter)) {
            AuthorizationFilter authzFilter = (AuthorizationFilter) filter;
            //only apply the unauthorizedUrl if they haven't explicitly configured one already:
            String existingUnauthorizedUrl = authzFilter.getUnauthorizedUrl();
            if (existingUnauthorizedUrl == null) {
                authzFilter.setUnauthorizedUrl(unauthorizedUrl);
            }
        }
    }

    private void applyGlobalPropertiesIfNecessary(Filter filter) {
        applyLoginUrlIfNecessary(filter);
        applySuccessUrlIfNecessary(filter);
        applyUnauthorizedUrlIfNecessary(filter);
    }


    private WebSecurityManager createSecurityManager() {
        logger.debug("Create SecurityManager...");
        AbstractAppShiroRealm realm = initRealm();

        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(realm);
        if (cfg.isCache()) {
            com.ifonly.cache.CacheManager cacheManager = CacheManagerFactory.createCacheManager();
            securityManager.setCacheManager(new ShiroCacheManager(cacheManager));
            securityManager.setCacheManager(new EhCacheManager());
        }

        final DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager();
        defaultWebSessionManager.setSessionIdCookieEnabled(true);
        SimpleCookie sessionIdCookie = new SimpleCookie();
        sessionIdCookie.setName("sid");
        sessionIdCookie.setHttpOnly(true);
        sessionIdCookie.setMaxAge(-1);
        defaultWebSessionManager.setSessionIdCookie(sessionIdCookie);
        // 默认一年过期时间
        defaultWebSessionManager.setGlobalSessionTimeout(cfg.getSecuritySessionExpired());
        securityManager.setSessionManager(defaultWebSessionManager);

        //rememberMe配置 : 1. rememberMeCookie 配置, 2. rememberMe 管理器配置, 3. 设置 rememberMe 管理器
        //rememberMeCookie
        SimpleCookie rememberMeCookie = new SimpleCookie();
        rememberMeCookie.setName("rememberMe");
        rememberMeCookie.setHttpOnly(true);
        rememberMeCookie.setMaxAge(cfg.getRememberMeCookieMaxAge()); //30天
        //rememberMe管理器
        CookieRememberMeManager rememberMeManager = new CookieRememberMeManager();
        rememberMeManager.setCipherKey(Base64.decode(cfg.getCipherKey()));
        rememberMeManager.setCookie(rememberMeCookie);
        //设置 rememberMe 管理器
        securityManager.setRememberMeManager(rememberMeManager);

        return securityManager;
    }

    private AbstractAppShiroRealm initRealm() {
        String realmClassName = cfg.getSecurityRealm();
        Object obj = null;
        if (Strings.isNullOrEmpty(realmClassName)) {
            @SuppressWarnings("unchecked")
            List<Class<?>> list = ClassContainer.getInstance().group(AbstractAppShiroRealm.class);
            if (list != null && list.size() > 0) {
                obj = ReflectUtils.newInstance(list.get(0));
            }
        } else {
            obj = ReflectUtils.newInstance(realmClassName);
        }

        if (obj == null) {
            throw new RuntimeException("Must map the class for AbstractAppShiroRealm");
        }

        if (!(obj instanceof AbstractAppShiroRealm)) {
            throw new RuntimeException("class " + realmClassName + "must be a instanceof AbstractAppShiroRealm");
        }
        AbstractAppShiroRealm realm = (AbstractAppShiroRealm) obj;
        realm.initialize();
        return realm;
    }

}
